using System;
using System.Xml.Serialization;
using System.IO;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Threading;

namespace RSSRadio.Model
{




    /// <summary>
    /// SubscriptionManager class is responsible for managing the collection of subscriptions. 
    /// In addion to CRUD operations this manger class provides auto download functionality 
    /// and scheduling of feed refreshes
    /// </summary>
    /// <remarks>
    /// Authors: Kiril Karaatanassov
    public class SubscriptionManager: SubscriptionUpdateListenerReplicatorImpl
    {
        private Dictionary<String, Subscription> SubscriptionMap;


        private bool autoDownload = false;


        public SubscriptionManager()
        {

            this.LoadSubscriptions();
        }

        public void LoadSubscriptions()
        {
            SubscriptionMap = new Dictionary<String, Subscription>();
            ReadTxt();
        }

        /// <summary>
        /// load subscription episodes for all feeds
        /// </summary>
        public void refreshFeeds()
        {
            foreach (Subscription feed in this.SubscriptionMap.Values)
            {
                feed.loadFeedAsync();
            }
        }

        /// <summary>
        /// load subscription episodes for all feeds and start download jobs
        /// </summary>
        public void DownloadLatestEpisodes()
        {
            if (!autoDownload)
            {
                autoDownload = true;
                refreshFeeds();
            }
        }
        /// <summary>
        /// Adds a Subscription to the SubscriptionManager xml file. An exception is thown if the rss file cannot be loaded from the URL or 
        /// if the URL already is added to SubscriptionManager
        /// </summary>
        /// <param name="URL">The podcast's unique database ID</param>
        /// <exception cref="ArgumentException">The podcast URL is already added</exception>
        public void AddSubscription(String podcastURL, int retentionDays)
        {
            if (!IsSubscribed(podcastURL))
            {
                Subscription temp = new Subscription();

                temp.URL = podcastURL;
                temp.RetnetionDays = retentionDays;

                temp.loadFeed();

                temp.addListener(this);

                lock (SubscriptionMap)
                {
                    SubscriptionMap.Add(podcastURL, temp);
                }
                StoreSubscriptions();

            }
            else
            {
                throw new ArgumentException("Podcast already added"); // already added
            }
        }

        public void setSubscriptionURL(Subscription feed, string newUrl)
        {
            if (!IsSubscribed(newUrl))
            {
                Subscription test = new Subscription();
                test.URL = newUrl;
                //Load feed to check if URL is ok.
                test.loadFeed();

                lock (SubscriptionMap)
                {
                    SubscriptionMap.Remove(feed.URL);
                    feed.URL = newUrl;
                    SubscriptionMap.Add(newUrl, feed);
                }
                StoreSubscriptions();
            }
            else
            {
                throw new ArgumentException("Podcast already added"); // already added
            }

        }


        /// <summary>
        /// Removes a Subscription from the SubscriptionManager inventory
        /// </summary>
        /// <param name="podcastID">The podcast's unique database ID</param>
        public bool RemoveSubscription(String podcastURL)
        {
            if (SubscriptionMap.ContainsKey(podcastURL))
            {
                lock (SubscriptionMap)
                {
                    SubscriptionMap.Remove(podcastURL);
                    StoreSubscriptions();
                }
                return true;
            }
            return false;
        }


        /// <summary>
        /// Gets a subscription by it's URL key.
        /// </summary>
        /// <param name="URL">The podcast's URL</param>
        /// <returns>The corespodnign to the URL record or null if no record is found</returns>
        public Subscription getSubscriptionByURL(String feedURL)
        {
            Subscription s = null;
            lock (SubscriptionMap)
            {
                SubscriptionMap.TryGetValue(feedURL, out s);
            }
            return s;
        }

        /// <summary>
        /// Returns true if podcast is already in SubscriptionManager.xml.
        /// </summary>
        /// <param name="URL">The podcast's URL</param>
        public bool IsSubscribed(String feedURL)
        {
            return getSubscriptionByURL(feedURL) != null;
        }

        public bool CheckIfAllLoaded()
        {
            bool res = true;
            lock (SubscriptionMap)
            {
                foreach (Subscription feed in SubscriptionMap.Values)
                {
                    if (feed.state == FeedState.LoadPending || feed.state == FeedState.NotLoaded)
                    {
                        res = false;
                        break;
                    }
                }
            }
            return res;
        }



        /// <summary>
        /// Stores SubscriptionList into SubscriptionManager.xml 
        /// Uses <seealso cref="Subscribe.WriteXml"/>
        /// </summary>
        public void StoreSubscriptions()
        {
            WriteTxt();
        }


        public int Count
        {
            get 
            {
                lock (SubscriptionMap)
                {
                    return SubscriptionMap.Count;
                }
            }
        }

        /// <summary>
        /// An array of SubscribeInfo 
        /// </summary>
        public Subscription[] Items
        {
            get
            {
                lock (SubscriptionMap)
                {
                    Subscription[] items = new Subscription[this.SubscriptionMap.Count];
                    SubscriptionMap.Values.CopyTo(items, 0);
                    return items;
                }
            }

            set
            {
                if (value == null) return;
                Subscription[] items = (Subscription[])value;
                SubscriptionMap.Clear();
                lock (SubscriptionMap)
                {
                    foreach (Subscription item in items)
                        SubscriptionMap.Add(item.URL, item);
                }
            }
        }

        private bool WriteTxt()
        {
            try
            {
                lock (SubscriptionMap)
                {
                    priv.ISubscriptionFileHandler fileHandler = new priv.SubscriptionFlatFileHandler();
                    return fileHandler.write(SubscriptionMap.Values);
                }
            }
            catch
            {
                return false;
            }
        }

        private bool ReadTxt()
        {
            priv.ISubscriptionFileHandler fileHandler = new priv.SubscriptionFlatFileHandler();
            return fileHandler.read(this.SubscriptionMap, this);
        }


        protected override void SubscriptionStateUpdatedImpl(String feedURL, FeedState oldState, FeedState newState)
        {
            if (autoDownload && CheckIfAllLoaded())
            {
                scheduleDownloadsOfLatestEpisodes();
                autoDownload = false;
            }
        }

        protected override void SubscriptionEpisodeUpdatedImpl(String feedURL, String episodeURL, String episodeDate, EpisodeState state)
        {
            if (state == EpisodeState.DownloadComplete)
            {
                Subscription feed = getSubscriptionByURL(feedURL);
                feed.lastDownloadedEpisodePublishDate = episodeDate;
                feed.lastDownloadedEpisodeUrl = episodeURL;
                StoreSubscriptions();
            }
        }

        protected override void SubscriptionDetailsUpdatedImpl(string podcastUrl)
        {
            this.StoreSubscriptions();
        }


        private void scheduleDownloadsOfLatestEpisodes()
        {
            Globals.ThreadPool.QueueUserWorkItem(new WaitCallback(scheduleDownloadsOfLatestEpisodesWorker));
        }
        private void scheduleDownloadsOfLatestEpisodesWorker(object o)
        {
            //Wait a bit to make sure things are calm
            Thread.Sleep(500);

            //First post purge of all stale podcasts
            Globals.ThreadPool.QueueUserWorkItem(new WaitCallback(Globals.DownloadedPodcasts.Purge));

            foreach (Subscription feed in SubscriptionMap.Values)
            {
                if (feed.episodes != null && feed.episodes.Length > 0)
                {
                    EpisodeDetails episode = feed.episodes[0];
                    //In auto mode skip latest episode if it has alreayd been downloaded
                    if (feed.lastDownloadedEpisodeUrl != episode.Url.ToString() ||
                        feed.lastDownloadedEpisodePublishDate != episode.Date)
                    {
                        PodcastActions.Download(feed, episode);
                    }
                }
            }

        }


    }
}